package network;

import exceptions.FileException;
import exceptions.MessageCreationException;
import exceptions.MessageLengthException;
import gui.ChatGUI;

import java.net.InetAddress;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.text.BadLocationException;

import signals.UserNotifications;
import signals.UserStatus;
import signals.Wizz;
import controller.ChatController;
import controller.Interface.GuiController;
import cs.remoteapp.Goodbye;
import cs.remoteapp.Hello;
import cs.remoteapp.Message;
import cs.remoteapp.TextMessage;
import cs.remoteapp.FileAnswer;
import cs.remoteapp.FileRequest;

import model.data.*;
import network.Interface.NiController;
import network.TCP.ClientTCP;
import network.TCP.TCPServer;

import network.udptools.UDPListener;
import network.udptools.UDPSender;
import network.udptools.UDPSettings;
import network.udptools.Interface.UDPListenerInterface;
import network.udptools.Interface.UDPSenderInterface;

/**
 * <li>The ChatNetwork class supports the communication between low layers</li>
 * <li>ChatController implements the interface NiController to communicate with the UDPSender</li>
 * 
 * @see ChatController
 * @see NiController
 * 
 * @author Christelle SIAGO HAPPI ChatNI
 */
public class ChatNI implements NiController {

	private UDPSenderInterface udpSender;
	private UDPListenerInterface udpListener;
	private controller.Interface.GuiController controller;
	private FileDescription fileToSend;
	private FileDescription receivedFile;
	private long receivedFileSize;
	private String mlog;

	/**
	 * The constructor of the ChatNI which launches UDPListener and UDPSender
	 * threads
	 */
	public ChatNI() {
		this.udpSender = new UDPSender();
		this.udpListener = new UDPListener(this);
		this.udpListener.StartListening();

	}

	/**
	 * set the controller of the ChatSystem
	 * 
	 * @param controller
	 *            : the controller of the ChatSystem
	 */
	public void setController(GuiController controller) {
		this.controller = controller;
	}

	/**
	 * set the GUI of the ChatSystem
	 */
	public void setGUI(ChatGUI gui) {
		gui = this.controller.getGUI();
	}

	/**
	 * sends a hello message in broadcast
	 */
	@Override
	public void sendHello(String username, boolean isBroadCast) {

		System.out.println(username + "> sendHello");

		byte[] myHelloByteArray;
		try {
			myHelloByteArray = MessagesFactory.getHello(username, isBroadCast);
			this.udpSender.sendBroadcastMessage(myHelloByteArray);
		} catch (MessageCreationException e) {
			e.printStackTrace();
		} catch (MessageLengthException e) {
			e.printStackTrace();
		}
	}

	/**
	 * send a Bye message when a user is disconnected
	 * 
	 * @param username
	 *            : the login of the user
	 */
	@Override
	public void sendBye(String username) {

		System.out.println(username + "> sendBye en Broadcast");

		byte[] myByeArray;
		try {
			myByeArray = MessagesFactory.getBye(username);
			this.udpSender.sendBroadcastMessage(myByeArray);
		} catch (MessageCreationException e) {

			e.printStackTrace();
		} catch (MessageLengthException e) {

			e.printStackTrace();
		}
	}

	/**
	 * send a wizz
	 */
	@Override
	public void sendWizz(String remoteUser, InetAddress address) {
		byte[] myWizzArray;
		try {
			myWizzArray = MessagesFactory.getWizz(remoteUser);
			this.udpSender.sendUnicastUDPMessage(myWizzArray, address);
		} catch (MessageCreationException e) {

			e.printStackTrace();
		} catch (MessageLengthException e) {

			e.printStackTrace();
		}
	}

	/**
	 * performs the received messages
	 * @throws BadLocationException 
	 */
	@Override
	public void receivedMessage(Message message, InetAddress adress)
			throws FileException, BadLocationException {

		if (message instanceof Hello) {
			System.out.println("A Hello has been received");
			// getting the hello message
			Hello hello = (Hello) message;

			System.out.println("it comes from " + hello.getUsername() + "@"
					+ adress);

			if (hello.isBroadcast()) {
				System.out.println("L'utilisateur " + message.getUsername()
						+ " just connected");
				System.out.println(message.getUsername()
						+ " expects an acquittalt\n");

				if (ChatController.isConnected()) {

					controller.newUser(hello.getUsername(), adress);
					this.sendHello(controller.getLocalUser(), false);
				}

			} else {

				System.out.println(controller.getLocalUser()
						+ ">I received an answer to my Hello\n");
				//controller.addUser(hello.getUsername(), adress);
			}
		}
		// Reception of the GoodBye message
		else if (message instanceof Goodbye) {
			System.out.println("A Bye was received");
			System.out.println("it comes from  " + message.getUsername() + "@"
					+ adress);
			Goodbye goodBye = (Goodbye) message;
			controller.removeUser(goodBye.getUsername());
		}
		// Reception of a textMessage
		else if (message instanceof TextMessage) {
			TextMessage text = (TextMessage) message;
			System.out.println("A Text Message was received");
			System.out.println("it comes from  " + text.getUsername() + "@"
					+ adress);

			controller.performReceivedMessage(text.getUsername(),
					text.getText());

			// Reception of a file request
		} else if (message instanceof FileRequest) {

			FileRequest fileRequest = (FileRequest) message;
			System.out.println("A transfer request file was received");
			System.out.println("it comes from  " + fileRequest.getUsername()
					+ "@" + adress);
			String remoteUser = fileRequest.getUsername();
			FileDescription file = new FileDescription(
					fileRequest.getFileName());
			long fileSize = fileRequest.getFileSize();

			System.out.println("fileSize to send equals ==>" + fileSize);

			// getting the answer of the remote to the sending file request
			boolean isAgree = this.controller.getGUI().responseToFileRequest(
					file, remoteUser);

			System.out.println("isAgree " + isAgree);

			if (isAgree) {
				this.fileTranfertAccepted(file, fileSize);
				this.controller.performSendAnswer(remoteUser, isAgree, file,
						fileSize);

				// TODO log
				System.out.println("Transfer request file from :" + remoteUser
						+ "@" + adress);
			}
		} else if (message instanceof FileAnswer) {

			FileAnswer fileAnswer = (FileAnswer) message;

			this.controller.getGUI().refreshFileTransferSate(
					fileAnswer.isAccepted());

			System.out.println("response gui to transfer = "
					+ fileAnswer.isAccepted());

			if (fileAnswer.isAccepted()) {
				InetAddress addressServer = this.controller.getUserList()
						.getUserAddress(fileAnswer.getUsername());
				short portReception = (short) UDPSettings.TCP_PORT_ECOUTE;

				System.out.println("remote address =" + addressServer);
				this.sendFile(addressServer, portReception);

				addLog("Response file transfer from:"
						+ fileAnswer.getUsername() + "\n Response :"
						+ fileAnswer.isAccepted());
			}

		} else if (message instanceof Wizz) {
			Wizz wizz = (Wizz) message;
			this.controller.getGUI().receiveWizz(wizz.getUsername());
		} else if (message instanceof UserStatus) {
			UserStatus userStatus = (UserStatus) message;
			this.controller.getGUI().receivedStatus(userStatus.getUsername(),
					userStatus.getStatus());
		} else if (message instanceof UserNotifications) {
			UserNotifications userNotif = (UserNotifications) message;
			this.controller.getGUI().receivedUserNotification(
					userNotif.getUsername(), userNotif.isIsWriting());
			System.out.println("Notification d'ecriture "
					+ userNotif.getUsername());
		}
	}

	/**
	 * send a text message to the remote user
	 */
	@Override
	public void sendMessage(String username, String text, InetAddress adresse) {

		byte[] myMessageByteArray;
		try {
			myMessageByteArray = MessagesFactory.getTextMessage(username, text);
			this.udpSender.sendUnicastUDPMessage(myMessageByteArray, adresse);
		} catch (MessageCreationException e) {
			e.printStackTrace();
		} catch (MessageLengthException e) {
			e.printStackTrace();
		}
	}

	/**
	 * called the remote accept the sending file request and create the file
	 * descrption to send
	 */
	@Override
	public void fileTranfertAccepted(FileDescription file, long size) {
		receivedFile = new FileDescription(file);
		receivedFileSize = size;

	}

	/**
	 * send a request to the remote if he wants the local user want to send him
	 * a file
	 */
	@Override
	public void sendFileRequest(InetAddress rAdress, String username,
			FileDescription file, long fileSize) {
		System.out.println(username + "> ask a file to send");

		byte[] myFileRequestArray;
		try {
			String fileName = file.getName();
			fileToSend = new FileDescription(file);
			myFileRequestArray = MessagesFactory.getFileRequest(username,
					fileName, fileSize);
			udpSender.sendUnicastUDPMessage(myFileRequestArray, rAdress);
		} catch (MessageCreationException | MessageLengthException e) {
			e.printStackTrace();
		}

	}

	/**
	 * send the answer of the remote
	 */
	@Override
	public void sendFileAnswer(String remoteUser, FileDescription file,
			boolean accept, long fileSize) {

		short portReception = (short) UDPSettings.TCP_PORT_ECOUTE;
		byte[] myFileAnswerArray;

		InetAddress adresse = this.controller.getUserList().getUserAddress(
				remoteUser);
		System.out.println("adresse du remote = " + adresse);

		try {
			myFileAnswerArray = MessagesFactory.getFileFileAnswer(remoteUser,
					file.getName(), accept);
			udpSender.sendUnicastUDPMessage(myFileAnswerArray, adresse);
		} catch (MessageCreationException e) {
			e.printStackTrace();
		} catch (MessageLengthException e) {

			e.printStackTrace();
		}
		if (accept) {
			receivedFile = file;

			receivedFileSize = fileSize;
			System.out.println("file reception= " + receivedFile.getFile());
			System.out.println("file size reception = " + receivedFileSize);

			// Launch of thread reception
			Thread t = (new Thread(new TCPServer(portReception, receivedFile,
					receivedFileSize)));
			t.start();
		}

	}

	/**
	 * send the file to the remote
	 */
	@Override
	public void sendFile(InetAddress addressServer, short portServer) {

		// Launch of thread emission

		System.out.println("file to send: " + fileToSend.toString());
		Thread t = (new Thread(new ClientTCP(addressServer, portServer,
				fileToSend)));
		t.start();

	}

	/**
	 * get the conversation historic
	 * 
	 * @param log
	 *            Login provided by the local user
	 */
	private void addLog(String log) {
		Date d = new Date();
		DateFormat dateFormat = new SimpleDateFormat("dd/MM/yy HH'h'mm");
		mlog += "[" + dateFormat.format(d) + "] " + log + "\n";

	}

	/**
	 * displays the conversation historic
	 * 
	 * @return the conversation historic
	 */
	private String displayLog() {
		return mlog;
	}

	/**
	 * send the status of a user
	 */
	@Override
	public void sendPresence(String username, Status s) {
		byte[] myStatusArray;
		InetAddress address = this.controller.getUserList().getUserAddress(
				username);
		try {
			myStatusArray = MessagesFactory.getUserStatus(username, s);
			this.udpSender.sendUnicastUDPMessage(myStatusArray, address);
		} catch (MessageCreationException e) {
			e.printStackTrace();
		} catch (MessageLengthException e) {
			e.printStackTrace();
		}
	}

	/**
	 * called when a user is typing a message
	 */
	@Override
	public void sendRemoteIsWriting(String rUserName, boolean isWriting) {
		byte[] myNotifArray;
		InetAddress address = this.controller.getUserList().getUserAddress(
				rUserName);
		try {
			myNotifArray = MessagesFactory.getUserNotificiation(rUserName,
					isWriting);
			this.udpSender.sendUnicastUDPMessage(myNotifArray, address);
		} catch (MessageCreationException e) {
			e.printStackTrace();
		} catch (MessageLengthException e) {
			e.printStackTrace();
		}

	}

}
